package org.reborn.engine.app

import org.reborn.engine.input._
import org.reborn.engine.input.jinput._
import org.reborn.engine.render.util.GLUtil
import org.reborn.engine.render.util.GLUtil._

import java.awt._
import java.awt.event._
import java.awt.image._

import javax.media.opengl._
import javax.media.opengl.awt._
import javax.media.opengl.GL2
import javax.media.opengl.fixedfunc.GLMatrixFunc

import javax.media.opengl.glu._
import com.jogamp.opengl.util._;

import java.io.PrintStream

trait GLApp extends GLEventListener{
  
  private var animator:Animator = null
  private var frame:Frame = null
  private var canvas:GLCanvas = null
  private var capabilities:GLCapabilities = null
  private val fullscreenSupport = new FullscreenSupport();
  private val displayModeSupport = new DisplayModeSupport(); 
  private var debugGL = false;
  private var traceOutputStream:PrintStream = null;
  private var immobilizeCursorFlag = false;
  
  private val robot = new Robot
  
  val glu = new GLU()
  var curTime:Long = 0L
  val util = GLUtil;
  val input = Input;

  final def reshape(drawable:GLAutoDrawable, x:Int, y:Int, width:Int, height:Int){
    reshapeScene(drawable,x,y,width,height)
  }
  
  final def display(drawable:GLAutoDrawable){
	//Check if the app needs to stop
	if(stopAppIf){
	  closeWindow()
	}
	
	//Check if we need to programmatically lock the mouse cursor position
	if(immobilizeCursorFlag){
	  val x = frame.getX + frame.getWidth / 2
	  val y = frame.getY + frame.getHeight / 2
	  robot.mouseMove(x,y)
	}
	
	//Calculate the elapsed time since the last frame
    val newTime = System.nanoTime() 
    val dt = (newTime - curTime).asInstanceOf[Float] / 1000000000.0f 
    curTime = newTime
  
    //Update input device states
    input.updateInputDevices
    
    //Call user function for updating game logic
    updateLogic(dt)
    
    //Call user function for drawing JOGL commands
    renderScene(drawable)
  } 
  
  final def init(drawable:GLAutoDrawable){
    curTime = System.nanoTime()
    
    if(debugGL)
      drawable.setGL(new DebugGL2(drawable.getGL.getGL2))
    if(traceOutputStream != null)
      drawable.setGL(new TraceGL2(drawable.getGL.getGL2,traceOutputStream))
    
    drawable.getGL.setSwapInterval(0);
    util.init(drawable.getGL.getGL2)
    
    initScene(drawable)
  }  
  
  final def dispose(drawable:GLAutoDrawable){
    deInitScene(drawable)
  }  
  
  final def displayChanged(drawable:GLAutoDrawable, modeChanged:Boolean, deviceChanged:Boolean){ }
 
  /**
   * Initializes the application from the supplied options and starts the
   * main game loop.
   * @param options Tells the app how to behave when it starts up.
   */
  final def startApp(options:GLAppOptions) {
    if(options.input == InputImplementation.JInput){
      input.mouse = JInput.GetDefaultMouse.getOrElse(null)
      input.keyboard = JInput.GetDefaultKeyboard.getOrElse(null)
    }
	  
	capabilities = new GLCapabilities(GLProfile.get(GLProfile.GL2));
    debugGL = options.debugGL
    traceOutputStream = options.traceOutputStream
    
	frame = new Frame(options.title)
	canvas = new GLCanvas(capabilities)
	  
	canvas.addGLEventListener(this)
	frame.add(canvas);
	frame.setSize(options.width, options.height);
	
    if(options.fullscreen){
      fullscreenSupport.setFullScreenIfAvailable(frame)
      if(fullscreenSupport.isFullScreen){
        displayModeSupport.changeDisplayMode(options.width,options.height,options.bitDepth,options.refreshRate)
      }
    }
   
	animator = new Animator(canvas)
	frame.addWindowListener(new WindowAdapter {
	  override def windowClosing(e: WindowEvent) {
	    new Thread(new Runnable{
	      def run(){
	        closeApp
	      }
	    }).start()
	  }
	})

	frame.setVisible(true)
    animator.setRunAsFastAsPossible(true)
	animator.start
  }
  
  final def closeApp() {
	animator.stop
	displayModeSupport.revertDisplayMode
    fullscreenSupport.leaveFullScreen
	System.exit(0)
  }

  /**
   * Allows the user to initialize openGL states or other data before
   * the scene is rendered. This method is only called once when the app
   * first starts up.
   * @param drawable reference to JOGL's drawable object
   */
  def initScene(drawable:GLAutoDrawable):Unit = {}
  
  /**
   * Allows handling the event when a window changes dimensions 
   * @param drawable
   * @param x The position of the window in the x dimension 
   * @param y The position of the window in the y dimension
   * @param width The width of the window
   * @param height The height of the window
   */
  def reshapeScene(drawable:GLAutoDrawable, x:Int, y:Int, width:Int, height:Int){
    val gl = drawable.getGL.getGL2
    import gl._
    import glu._
    
    /* This code comes from the Gears Demo from the JOGL site.  Changed slightly
     * because scala lets us do some things that Java can't
     */
    
    val h:Float = width/height;
            
    glMatrixMode(GLMatrixFunc.GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f,h,0.001f, 50.0f)

    glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
    glLoadIdentity();
  }
  
  /**
   * The main rendering logic of the app. Put whatever JOGL code you want to render
   * in here.
   * @param drawable
   */
  def renderScene(drawable:GLAutoDrawable):Unit = {}
  
  /**
   * This method will be called each frame before renderScene. It allows the application
   * to update any state that changes on a per-frame basis.
   * @param seconds The amount of time in seconds that has ellapsed since the last frame
   */
  def updateLogic(seconds:Float):Unit = {}
  
  /**
   * Allows the user to de-initialize or clean up any references to data 
   * that should be handled when the application shuts down. This method 
   * is only called once when the app is shutting down.
   * @param drawable reference to JOGL's drawable object
   */
  def deInitScene(drawable:GLAutoDrawable):Unit = {}
  
  /**
   * Allows the application to specify a condition that should stop the application
   * and shut it down. This method is checked each frame to see if the conditions
   * have been met. By default, this method returns true if the Escape key is pressed.
   * @return true of the application should shut down, or false if it should continue
   * running.
   */
  def stopAppIf():Boolean = {
	return input.keyboard.keydown(KeyboardKey.Esc)
  }
  
  /**
   * Sends a signal to the application that is should shut down. This method can be
   * called at will from anywhere inside the application and it will attempt to 
   * stop the app gracefully.
   */
  def closeWindow(){
	val windowClosing = new WindowEvent(frame, WindowEvent.WINDOW_CLOSING);
	Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(windowClosing);
  }
  
  /**
   * Allows the application to show or hide the cursor programmatically.
   * @param show whether or not the cursor should be shown
   */
  def showCursor(show:Boolean){
	val cursor = 
	  if(show)
		new Cursor(Cursor.DEFAULT_CURSOR)
	  else
	 	Toolkit.getDefaultToolkit().createCustomCursor(new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB), new Point( 0, 0), "" );
	
	frame.setCursor(cursor);
  }
  
  /**
   * Locks the cursor position at (width/2, height/2) at the beginning of each frame.
   * This is useful for making sure focus remains inside the application window.
   * @param immobilize whether or not to immobilize the cursor
   */
  def immobilizeCursor(immobilize:Boolean){
	immobilizeCursorFlag = immobilize
  }
}
